﻿using System.Windows;
using System.Collections.Generic;
using AnimatingObjectsAlongAPath;
using System.Linq;
using System;

#if WINDOWS_PHONE
using DeepEarth.Core.Utilities.WP7;
#endif
namespace DeepEarth.Toolkit.Geometry.Clipping
{
    public static class LineClipper
    {
        // As Silverlight can't handle drawing larger than 32767 screen pixels, we clip shapes that will be rendered larger than this.
        public static double DefaultMaxBound = 16848;

        public static List<List<Point>> Clip (List<Point> input, double maxBound)
        {
            List<List<Point>> lineSegments = new List<List<Point>>();

            if (input.Count() < 2)
            {
                return lineSegments;
            }

            List<Line> boundaries = Helpers.CreateBoundedClippingLines(maxBound);

            List<Point> lineSegment = new List<Point>();

            for(int index = 0; index < input.Count - 1; index++)
            {
                Point firstPoint = input[index];
                Point secondPoint = input[index + 1];
                Line currentEdge = new Line
                {
                    X1 = firstPoint.X,
                    Y1 = firstPoint.Y,
                    X2 = secondPoint.X,
                    Y2 = secondPoint.Y
                };

                int intersectionCount = 0;
                bool startsInBounds = InBounds(firstPoint, maxBound);
                
                List<Tuple<bool, double, Point>>hits = new List<Tuple<bool,double,Point>>();

                foreach (var boundary in boundaries)
                {
                    Tuple<bool, double, Point> hitInfo = GetIntersection(currentEdge, boundary);

                    if (hitInfo.Item1)
                    {
                        hits.Add(hitInfo);
                        intersectionCount++;
                    }
                }

                switch(intersectionCount)
                {
                    case 0:
                        if (startsInBounds)
                        {
                            lineSegment.Add(firstPoint);
                            lineSegment.Add(secondPoint);
                        }
                        else
                        {
                            // do nothing as the current edge is wholly out of bounds
                        }
                        break;

                    case 1:
                        if (startsInBounds)
                        {
                            lineSegment.Add(firstPoint);
                            lineSegment.Add(hits.FirstOrDefault().Item3);

                            // moving out of bounds, close of the current line
                            lineSegments.Add(lineSegment);
                            lineSegment = new List<Point>();
                        }
                        else
                        {
                            // moving into bounds add points
                            lineSegment.Add(hits.FirstOrDefault().Item3);
                            lineSegment.Add(secondPoint);
                        }
                        break;
                    case 2:
                        if (startsInBounds)
                        {
                            // not possible
                        }
                        else
                        {
                            hits = hits.OrderBy(t => t.Item2).ToList();
                            lineSegment.Add(hits[0].Item3);
                            lineSegment.Add(hits[1].Item3);
                            lineSegments.Add(lineSegment);
                            lineSegment = new List<Point>();
                        }
                        break;
                }
            }

            // linesegments are added when we cross out of the boundary. If we never cross we have never added
            // so check for that condition here.
            if (lineSegment.Any())
            {
                lineSegments.Add(lineSegment);
            }

            return lineSegments;
        }

        static bool InBounds(Point p, double maxBound)
        {
            return
                p.X > -maxBound &&
                p.X < maxBound &&
                p.Y > -maxBound &&
                p.Y < maxBound;
        }

        static bool NearestIntersection (Line testVector, List<Line> boundaries, ref Point p)
        {
            double minLength = double.MaxValue;

            foreach (var boundary in boundaries)
            {
                Point intersect = new Point();
                if (Helpers.DoLinesIntersect(testVector, boundary, ref intersect))
                {
                    Line segment = new Line
                    {
                        X1 = testVector.X1,
                        Y1 = testVector.Y1,
                        X2 = intersect.X,
                        Y2 = intersect.Y
                    };

                    double segmentLength = segment.Length();

                    if (segmentLength < minLength)
                    {
                        minLength = segmentLength;
                        p = intersect;
                    }
                }            
            }

            return minLength < double.MaxValue;
        }

        static Tuple<bool, double, Point> GetIntersection(Line testVector, Line boundary)
        {
            Point intersection = new Point();
            bool hit = Helpers.DoLinesIntersect(testVector, boundary, ref intersection);
            double dist = 0;
            if (hit)
            {
                Line segment = new Line
                {
                    X1 = testVector.X1,
                    Y1 = testVector.Y1,
                    X2 = intersection.X,
                    Y2 = intersection.Y
                };

                dist = segment.Length();
            }

            return new Tuple<bool, double, Point>(hit, dist, intersection);
        }

        static double Length(this Line line)
        {
            return Math.Sqrt(Math.Pow(line.X2 - line.X1, 2) + Math.Pow(line.Y2 - line.Y1, 2));
        }
    }
}
